package com.yh.service.impl;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.baidu.fsg.uid.UidGenerator;
import com.yh.dao.OrderDao;
import com.yh.dao.ScheduleDao;
import com.yh.dao.UserDao;
import com.yh.entity.Movie;
import com.yh.entity.Orderinfo;
import com.yh.entity.Schedule;
import com.yh.entity.User;
import com.yh.service.CinemaService;
import com.yh.service.MovieService;
import com.yh.service.OrderService;
import com.yh.utils.Constants;
import com.yh.vo.CustomPageAndNavigate;
import com.yh.vo.OrderVO;
import com.yh.vo.ScheduleVO;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.log4j.Logger;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author yuhuan
 * @date 2022/01/13
 */
@DubboService
public class OrderServiceImpl implements OrderService {
    /**
     * 日志
     */
    private static final Logger logger = Logger.getLogger(OrderServiceImpl.class);

    @Autowired
    private OrderDao orderDao;
    @Autowired
    private ScheduleDao scheduleDao;
    @Autowired
    private UserDao userDao;
    @DubboReference(check = false)
    private CinemaService cinemaService;
    @DubboReference(check = false)
    private MovieService movieService;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private UidGenerator uidGenerator;

    @Override
    public CustomPageAndNavigate findOrderAll(Integer pageNum, Integer pageSize) {
        List<Orderinfo> orderList = orderDao.findOrderAll();
        List<OrderVO> voList = new ArrayList<>();
        orderList.forEach(orderinfo -> {
            OrderVO orderDetail = findOrderToVo(orderinfo, true);
            voList.add(orderDetail);
        });
        return new CustomPageAndNavigate(pageNum, pageSize, voList);
    }

    @Override
    public OrderVO findOrderDetailById(String orderId) {
        Orderinfo orderinfo = orderDao.findOrderByOrderId(orderId);
        if (ObjectUtils.isEmpty(orderinfo)) {
            return null;
        }
        OrderVO orderToVo = findOrderToVo(orderinfo, false);
        return orderToVo;
    }

    @Override
    public List<OrderVO> findOrderDetailByUserId(String userId) {
        // 查询用户名下的所有订单信息
        List<Orderinfo> orderinfos = orderDao.findOrderByUserId(userId);
        if (ObjectUtils.isEmpty(orderinfos)) {
            return new ArrayList<>();
        }
        // 将订单信息转换成订单详情
        List<OrderVO> orderDetailList = new ArrayList<>();
        for (Orderinfo orderinfo : orderinfos) {
            OrderVO orderVO = findOrderDetailById(orderinfo.getOrderId());
            orderDetailList.add(orderVO);
        }
        return orderDetailList;
    }

    @Override
    public OrderVO createOrder(OrderVO orderVo) {
        Orderinfo order = new Orderinfo();
        // 提前取出用户权限
        int userRole = orderVo.getUserRole();
        boolean force = orderVo.getForce();
        BeanUtils.copyProperties(orderVo, order);
        // 如果订单已经被售出或者片场已经结束则拒绝创建订单
        if (!isSold(order.getOrderPosition(), order.getScheduleId(), userRole, force)) {
            orderVo.setCreateState(false);
            return orderVo;
        }
        // 占座
        occupySeat(orderVo.getOrderPosition(), orderVo.getScheduleId());
        orderVo.setCreateState(true);
        // 创建订单ID(通过Uid创建订单流水号)
        String orderId = String.valueOf(uidGenerator.getUID());
        order.setOrderId(orderId);
        // 设置订单状态为待支付
        order.setOrderState(Constants.OrderData.WAIT_PAY);
        // 记录订单的创建时间
        Date date = new Date();
        order.setOrderTime(date);
        // 将订单保存到数据库
        orderDao.addOrder(order);
        // 更新最新的订单信息
        orderVo.setOrderinfo(order);
        // 发送订单的ID到延迟队列，方便释放过期订单占用的座位
        rabbitTemplate.convertAndSend(
                Constants.RabbitMqData.DELAY_EXCHANGE_NAME,
                Constants.RabbitMqData.DELAY_BINDING_KEY,
                orderId, message -> {
                    // 设置延迟时间（单位：毫秒）
                    // 该延迟时间需要与订单的过期时间保存一致,"300000"
                    message.getMessageProperties().setDelay(300000);
                    return message;
                });
        return orderVo;
    }

    @Override
    public int deleteOrderById(String orderId) {
        Orderinfo order = orderDao.findOrderByOrderId(orderId);
        if (ObjectUtils.isEmpty(order)) {
            return Constants.ResponseDataState.RESP_SUCCESS;
        }
        // 如果正在退票就不能删除订单
        if (order.getOrderState() == Constants.OrderData.REFUNDING) {
            return Constants.ResponseDataState.DELETE_FAIL_OF_REFUNDING;
        }
        // 如果订单的状态是待支付，就释放订单的座位
        if (order.getOrderState() == Constants.OrderData.WAIT_PAY) {
            // 释放订单占用的座位
            releaseSeat(order);
        }
        // 将订单标记为已删除
        orderDao.recordInertOrderByOrderId(orderId);
        return Constants.ResponseDataState.RESP_SUCCESS;
    }

    /**
     * 检查并释放过期订单占用的座位（订单将会被标记为已删除，但是不会从数据库中真正移除）
     * 如果需要彻底移除，请使用'clearInertOrder'来完成
     */
    @Override
    public void checkExpiredOrder() {
        int releaseSeatNum = 0;
        logger.debug(">>>>> 执行定时任务《释放被过期订单占用的座位》");
        // 找到所有还未支付的订单信息
        List<Orderinfo> waitPayOrders = orderDao.findOrderByOrderState(Constants.OrderData.WAIT_PAY);
        for (Orderinfo waitPayOrder : waitPayOrders) {
            releaseSeatNum = orderCheck(waitPayOrder);
        }
        logger.debug(">>>>> 本次检查完毕-总共释放了" + releaseSeatNum + "个座位");
    }

    /**
     * 检查订单是否过期（检查单个订单）
     *
     * @param order 等待检查的订单
     * @return 被释放了座位的订单数量
     */
    @Override
    public int orderCheck(Orderinfo order) {
        int releaseSeatNum = 0;
        // 计算订单从创建到现在的时间差（单位：分钟）
        long timeDifference = DateUtil.between(order.getOrderTime(), new Date(), DateUnit.MINUTE);
        // 如果时差大于订单的有效时间，就表示订单已经过期
        if (timeDifference > Constants.OrderData.ORDER_EFFECTIVE_TIME) {
            System.out.print(order.getOrderId() + "号订单已过期-");
            logger.debug("订单创建时间：" + DateUtil.formatDateTime(order.getOrderTime()));
            // 将订单标记为已删除
            orderDao.recordInertOrderByOrderId(order.getOrderId());
            // 释放被占用的座位
            releaseSeatNum = releaseSeatNum + releaseSeat(order);
        }
        return releaseSeatNum;
    }

    /**
     * 查询单个订单的详细信息（返回订单响应模型）
     *
     * @aotuUser 是否需要封装用户信息
     */
    private OrderVO findOrderToVo(Orderinfo order, boolean aotuUser) {
        OrderVO orderVo = new OrderVO(order);
        if (aotuUser) {
            // 封装用户信息
            User user = userDao.findUserById(order.getUserId());
            orderVo.setUserName(user.getUserName());
            orderVo.setUserAccount(user.getUserAccount());
        }
        // 场次详情
        ScheduleVO scheduleVO = cinemaService.findScheduleDetailById(order.getScheduleId());
        orderVo.setScheduleVO(scheduleVO);
        // 电影实体信息
        Movie movie = movieService.findMovieByIdNeedIgnoreState(scheduleVO.getSchedule().getMovieId());
        orderVo.setMovie(movie);
        // 计算订单的剩余有效时间
        long surplusEffectiveTime = countSurplusEffectiveTime(order.getOrderTime());
        orderVo.setSurplusEffectiveTime(surplusEffectiveTime);
        return orderVo;
    }

    /**
     * 释放被占用的座位。返回释放的数量；没有被占用的座位将返回'0'
     */
    public int releaseSeat(Orderinfo orderinfo) {
        // 记录释放座位的数量
        int releaseNum = 0;
        Schedule schedule = scheduleDao.findScheduleById(orderinfo.getScheduleId());
        // 获取所有被购买的座位信息
        List<String> seatIds = schedule.getSeatIds();
        // 获取当前订单购买的座位信息
        List<String> orderPositions = orderinfo.getOrderPosition();
        // 删除被占用的座位
        for (int i = 0; i < seatIds.size(); i++) {
            for (String orderPosition : orderPositions) {
                if (orderPosition.equals(seatIds.get(i))) {
                    seatIds.remove(i);
                    releaseNum++;
                    logger.debug("<<<<< " + orderPosition + "号座位被释放");
                }
            }
        }
        // 将删除后的记录更新到数据
        schedule.setSeatIds(seatIds);
        scheduleDao.updateSeatToSchedule(schedule);
        return releaseNum;
    }

    /**
     * 清除订单并释放被占用的座位；该操作是不可恢复的，执行后将会从数据库中彻底的移除订单信息
     */
    private void clearInertOrder(Orderinfo order) {
        // 释放被占用的座位
        releaseSeat(order);
        clearOrder(order.getOrderId());
    }

    /**
     * 清除订单（交给消息队列来执行删除工作，将订单从数据库中彻底移除）
     */
    private void clearOrder(String orderId) {
        // 将订单ID发布到删除队列
        rabbitTemplate.convertAndSend(
                Constants.RabbitMqData.ORDER_EXCHANGE_NAME,
                Constants.RabbitMqData.ORDER_BINDING_KEY,
                orderId);
    }

    /**
     * 计算订单的剩余有效时间
     */
    private long countSurplusEffectiveTime(Date orderCreateTime) {
        // 计算订单从创建开始到现在的时间差（单位：秒）
        long timeDifference = DateUtil.between(orderCreateTime, new Date(), DateUnit.SECOND);
        // 计算订单总的有效时间（单位：秒）
        long effectiveTime = Constants.OrderData.ORDER_EFFECTIVE_TIME * 60;
        // 如果时差小于有效时间，就计算剩余有效时间返回
        if (timeDifference < effectiveTime) {
            return effectiveTime - timeDifference;
        }
        return 0;
    }

    /**
     * 确认座位是否已经被售出，或者片场是否满足售出条件（只能售出还未开播的片场）
     * 为了方便测试，可以通过管理员的权限强制售出
     *
     * @seatPositions 位置信息
     * @scheduleId 片场ID
     * @userRole 用户角色。如果是管理员，可以结合'force'强制创建订单；仅限片场已经结束的情况
     */
    public boolean isSold(List<String> seatPositions, int scheduleId, int userRole, boolean force) {
        if (ObjectUtils.isEmpty(seatPositions)) {
            logger.debug(">>>>> 没有检测到任何被选中的座位，被判定为无效确认");
            return false;
        }
        Schedule schedule = scheduleDao.findScheduleById(scheduleId);
        if (schedule.getScheduleStartTime().before(new Date())) {
            logger.warn(">>>>> 检查到违规操作！因为" + scheduleId + "已经结束片场了");
            if (userRole != Constants.LoginData.DEVELOPER_ROLE || !force) {
                return false;
            }
            logger.info(">>>>> 检查到是管理员在强制创建订单");
        }
        for (String seatPosition : seatPositions) {
            // 如果当前座位已经被售卖，直接结束
            if (schedule.getSeatIds().contains(seatPosition)) {
                logger.debug(seatPosition + "已经被出售!");
                return false;
            }
        }
        return true;
    }

    /**
     * 给当前订单的座位占座，在数据库中将该座位的信息标记为已售出的状态
     */
    public int occupySeat(List<String> seatPositions, int scheduleId) {
        Schedule schedule = scheduleDao.findScheduleById(scheduleId);
        List<String> soldSeatIds = schedule.getSeatIds();
        // 将当前订单中的座位信息都添加到该场次中占座
        soldSeatIds.addAll(seatPositions);
        schedule.setSeatIds(soldSeatIds);
        int result = scheduleDao.updateSeatToSchedule(schedule);
        return result;
    }
}
